package com.example.crawler.youtube.extractor;

import com.alibaba.fastjson.JSONObject;
import com.example.crawler.youtube.Extractor;
import com.example.crawler.youtube.exception.YoutubeException;
import com.example.crawler.youtube.model.ConvertResult;
import com.example.crawler.youtube.model.YoutubeExtractorResult;
import com.example.crawler.youtube.model.third.HttpUrlConnectionUtil;
import com.example.crawler.youtube.model.third.Mp4Video;
import com.example.crawler.youtube.model.third.PriorityFilter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
public abstract class ThirdPlatformExtractor implements Extractor<YoutubeExtractorResult>, Ordered {

    private Logger logger = LoggerFactory.getLogger(ThirdPlatformExtractor.class);

    public static final Pattern patYouTubePageLink = Pattern.compile("(http|https)://(www\\.|m.|)youtube\\.com/watch\\?v=(.+?)( |\\z|&)");
    public static final Pattern patYouTubeShortLink = Pattern.compile("(http|https)://(www\\.|)youtu.be/(.+?)( |\\z|&)");
    public static final String SUCCESS_ERROR = "false";
    public static final String SEPARATOR_URI = "?";
    public static final String SEPARATOR_PARAM = "&";
    public static final String PARAM_SEPARATOR = "=";
    private Set<String> ignoreParamSet = new HashSet<>();

    public void addIgnoreParam(String param) {
        ignoreParamSet.add(param);
    }

    @Override
    public ConvertResult<YoutubeExtractorResult> convert(String url) throws YoutubeException {
        JSONObject jsonObject = sendRequestAndGetResponse(url);

        String data = jsonObject.getString("data");
        List<Mp4Video> mp4Videos = parseVideos(data);
        if (CollectionUtils.isEmpty(mp4Videos)) {
            throw new YoutubeException(url, "没有找到对应的视频下载地址");
        }

        return getExtractorResult(mp4Videos);
    }

    /**
     * 提取获取到的视频规格列表，匹配出合适的视频信息
     *
     * @param mp4Videos
     * @return
     */
    private ConvertResult<YoutubeExtractorResult> getExtractorResult(List<Mp4Video> mp4Videos) {
        List<PriorityFilter> filters = PriorityFilter.getFilter();

        for (PriorityFilter filter : filters) {
            Optional<Mp4Video> optional = mp4Videos.stream()
                    .filter(item -> Objects.equals(item.isHasAudio(), filter.isHasAudio()) && Objects.equals(item.getQuality(), filter.getQuality()))
                    .findFirst();
            if (optional.isPresent()) {
                ConvertResult<YoutubeExtractorResult> result = new ConvertResult<>();
                Mp4Video v = optional.get();
                YoutubeExtractorResult extractorResult = new YoutubeExtractorResult();
                extractorResult.setTargetUrl(buildUrl(v.getUrl()));
                result.setData(extractorResult);
                result.setSuccess(true);
                return result;
            }
        }

        throw new YoutubeException("没有过滤出对应的清晰度视频");
    }


    private String buildUrl(String url) {
        String requestUri = StringUtils.substringBefore(url, SEPARATOR_URI);
        String requestParameter = StringUtils.substringAfter(url, SEPARATOR_URI);
        String[] paramArray = StringUtils.split(requestParameter, SEPARATOR_PARAM);
        List<String> paramList = new ArrayList<>(paramArray.length);
        for (String p : paramArray) {
            boolean ignore = ignoreParam(p);
            if (ignore) {
                continue;
            }
            paramList.add(p);
        }
        String join = StringUtils.join(paramList, SEPARATOR_PARAM);
        return String.format("%s?%s", requestUri, join);
    }

    /**
     * 存在忽略的请求参数名
     *
     * @param param key=value
     * @return
     */
    private boolean ignoreParam(String param) {
        if (!StringUtils.contains(param, PARAM_SEPARATOR)) {
            return true;
        }
        String[] split = StringUtils.split(param, PARAM_SEPARATOR);
        return ignoreParamSet.contains(split[0]);
    }

    private JSONObject sendRequestAndGetResponse(String youtubeVideoUrl) {
        int retryCount = 3;
        AtomicInteger num = new AtomicInteger(1);
        try {
            while (true) {
                logger.info("第[{}]次通过网站[{}]获取视频[{}]下载地址", num.get(), name(), youtubeVideoUrl);
                String response = postRequest(youtubeVideoUrl);
                JSONObject jsonObject = JSONObject.parseObject(response);
                String error = jsonObject.getString("error");
                if (Objects.equals(SUCCESS_ERROR, error)) {
                    jsonObject.put("website", name());
                    return jsonObject;
                }
                if (num.get() >= retryCount) {
                    break;
                }
                num.incrementAndGet();
                sleep();
            }
        } catch (Exception e) {
            logger.error("请求第三方平台[{}]转换异常!", name(), e);
        }
        throw new YoutubeException(youtubeVideoUrl, "未解析到视频下载地址");
    }

    /**
     * 获取请求URL
     *
     * @param youtubeVideoUrl
     * @return
     */
    protected abstract String getRequestUrl(String youtubeVideoUrl);

    /**
     * 获取请求参数
     *
     * @param youtubeVideoUrl
     * @return
     */
    protected abstract Map<String, String> getRequestParameterMap(String youtubeVideoUrl);

    private String getRequestParameter(Map<String, String> param) {
        Set<Map.Entry<String, String>> entries = param.entrySet();
        String paramStr = entries.stream()
                .map(item -> String.format("%s=%s", item.getKey(), item.getValue()))
                .collect(Collectors.joining("&"));
        return paramStr;
    }

    protected Map<String, String> getHeaderFields(String youtubeVideoUrl) {
        return null;
    }

    /**
     * post 请求
     *
     * @param youtubeVideoUrl
     * @return
     */
    private String postRequest(String youtubeVideoUrl) {
        String requestParameter = getRequestParameter(getRequestParameterMap(youtubeVideoUrl));
        return HttpUrlConnectionUtil.post(getRequestUrl(youtubeVideoUrl), requestParameter, getHeaderFields(youtubeVideoUrl));
    }

    protected String getVideoId(String youtubeVideoUrl) {
        Matcher mat = patYouTubePageLink.matcher(youtubeVideoUrl);
        if (mat.find()) {
            return mat.group(3);
        } else {
            mat = patYouTubeShortLink.matcher(youtubeVideoUrl);
            if (mat.find()) {
                return mat.group(3);
            } else if (youtubeVideoUrl.matches("\\p{Graph}+?")) {
                return youtubeVideoUrl;
            }
        }

        return null;
    }

    /**
     * data -> data -> videos -> mp4
     *
     * @param json
     * @return
     */
    private List<Mp4Video> parseVideos(String json) {
        JSONObject jsonObject = JSONObject.parseObject(json);
        JSONObject data = parseObject(jsonObject, "data", JSONObject.class);
        JSONObject videos = parseObject(data, "videos", JSONObject.class);
        String mp4 = videos.getString("mp4");
        List<Mp4Video> mp4Videos = JSONObject.parseArray(mp4, Mp4Video.class);
        return mp4Videos;
    }

    private <T> T parseObject(JSONObject json, String key, Class<T> clazz) {
        String data = json.getString(key);
        if (StringUtils.isBlank(key)) {
            throw new YoutubeException(null, String.format("读取解析的JSON字符串key:%s对应的数据为空", key));
        }
        return JSONObject.parseObject(data, clazz);
    }

    /**
     * 休眠2s，可以根据不同站点分别设置休眠时长
     */
    private void sleep() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean enable() {
        return true;
    }

    @Override
    public int getOrder() {
        return 10;
    }
}
